﻿using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Text;
using System.Text.RegularExpressions;

namespace net.xBei.Helper {
    /// <summary>
    /// 日期时间帮助类
    /// </summary>
    public static class DatetimeHelper {
        /// <summary>
        /// Unix新纪元
        /// </summary>
        public static readonly DateTime UNIX_EPOCH = new(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
        /// <summary>
        /// 
        /// </summary>
        /// <param name="dateTime"></param>
        /// <param name="end"></param>
        /// <param name="timeZone"></param>
        /// <returns></returns>
        public static DateTime FixUtcDay(this DateTime dateTime, bool end = false, int timeZone = 8) {
            return end
                ? dateTime.AddHours(timeZone).Date.AddDays(1).AddHours(-timeZone)
                : dateTime.AddHours(timeZone).Date.AddHours(-timeZone);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="dateTime"></param>
        /// <param name="hours"></param>
        /// <param name="minutes"></param>
        /// <param name="seconds"></param>
        /// <returns></returns>
        public static DateTime SetTime(this DateTime dateTime, int hours, int minutes, int seconds)
            => SetHours(dateTime, hours).SetMinutes(minutes).SetSeconds(seconds);
        /// <summary>
        /// 设置时间的“小时”（0-23）
        /// 24 ==  0
        /// -1 == 23
        /// </summary>
        /// <param name="dateTime"></param>
        /// <param name="hours"></param>
        /// <returns></returns>
        public static DateTime SetHours(this DateTime dateTime, int hours) {
            hours = hours > 24 || hours < -24 ? hours % 24 : hours;
            hours = hours < 0 ? 24 + hours : hours;
            return dateTime.AddHours(hours - dateTime.Hour);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="dateTime"></param>
        /// <param name="minutes"></param>
        /// <returns></returns>
        public static DateTime SetMinutes(this DateTime dateTime, int minutes) {
            minutes = minutes > 60 || minutes < -60 ? minutes % 60 : minutes;
            minutes = minutes < 0 ? 60 + minutes : minutes;
            return dateTime.AddMinutes(minutes - dateTime.Minute);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="dateTime"></param>
        /// <param name="seconds"></param>
        /// <returns></returns>
        public static DateTime SetSeconds(this DateTime dateTime, int seconds) {
            seconds = seconds > 60 || seconds < -60 ? seconds % 60 : seconds;
            seconds = seconds < 0 ? 60 + seconds : seconds;
            return dateTime.AddSeconds(seconds - dateTime.Second);
        }
        #region 获取日期数据
        /// <summary>
        /// <paramref name="data"/>转换成<see cref="DateTime"/>
        /// </summary>
        /// <param name="data"></param>
        /// <param name="dv"></param>
        /// <returns></returns>
        public static DateTime GetDateTime(this object data, DateTime dv) {
            if (data == null || data is DBNull) {
                return dv;
            }
            if (data is DateTime dt)
                return dt;
            if (data is string strDataTime) {
                return DateTime.TryParse(strDataTime, out var result) ? result : dv;
            }
            if (data is long ticks) {
                return ticks >= DateTime.MinValue.Ticks && ticks <= DateTime.MaxValue.Ticks
                        ? new DateTime(ticks)
                        : dv;
            }
            return dv;
        }
        #endregion
        #region 时区
        static readonly Regex regTimeZone = new("^([+-])(\\d{2}):(\\d{2})$", RegexOptions.Compiled);
        static readonly Regex regTimeZone2 = new("^([+-])(\\d+)$", RegexOptions.Compiled);
        /// <summary>
        /// 是否有效的时区
        /// </summary>
        /// <param name="v"></param>
        /// <returns></returns>
        public static bool IsValidTimeZone(string? v) => !string.IsNullOrWhiteSpace(v) && (regTimeZone.IsMatch(v) || regTimeZone2.IsMatch(v));
        /// <summary>
        /// 从<paramref name="offsetStr"/>获取时区偏移量（分钟）
        /// </summary>
        /// <param name="offsetStr"></param>
        /// <returns></returns>
        public static int GetTimeZoneOffsetMinutes(string offsetStr) {
            var m = regTimeZone.Match(offsetStr);
            if (m.Success) {
                return (m.Groups[1].Value == "-" ? -1 : 1)
                    * (m.Groups[2].Value.GetInt() * 60 + m.Groups[offsetStr].Value.GetInt());
            }
            m = regTimeZone2.Match(offsetStr);
            return (m.Groups[1].Value == "-" ? 1 : -1)
                    * m.Groups[2].Value.GetInt();
        }
        /// <summary>
        /// 从<paramref name="offsetStr"/>获取时区偏移量。
        /// </summary>
        /// <example>
        /// 如果“本地时间”时间是东八区，即“offset”是“-8小时”，那么以下成立。
        /// DateTime.UtcNow.Add(offset) Eq DateTime.Now
        /// </example>
        /// <param name="offsetStr">支持：“-480”、“+08:00”</param>
        /// <param name="minutes">UTC相对于“本地”时间的偏移量，比如“本地”时间是东八区，那么就是“-8小时”（表示UTC时间比“本地”时间晚了8小时）</param>
        /// <returns></returns>
        public static bool TryGetTimeZoneOffset(this string? offsetStr, [NotNullWhen(true)] out int? minutes) {
            minutes = default;
            if (!IsValidTimeZone(offsetStr)) { return false; }
            var m = regTimeZone.Match(offsetStr!);
            if (m.Success) {
                minutes = (m.Groups[1].Value == "-" ? -1 : 1)
                    * (m.Groups[2].Value.GetInt() * 60 + m.Groups[offsetStr!].Value.GetInt());
                return minutes.HasValue;
            }
            m = regTimeZone2.Match(offsetStr!);
            minutes = (m.Groups[1].Value == "-" ? 1 : -1)
                    * m.Groups[2].Value.GetInt();
            return minutes.HasValue;
        }
        /// <summary>
        /// 从<paramref name="offsetStr"/>获取时区偏移量。
        /// </summary>
        /// <example>
        /// 如果“本地时间”时间是东八区，即“offset”是“-8小时”，那么以下成立。
        /// DateTime.UtcNow.Add(offset) Eq DateTime.Now
        /// </example>
        /// <param name="offsetStr">支持：“-480”、“+08:00”</param>
        /// <param name="offset">UTC相对于“本地”时间的偏移量，比如“本地”时间是东八区，那么就是“-8小时”（表示UTC时间比“本地”时间晚了8小时）</param>
        /// <returns></returns>
        public static bool TryGetTimeZoneOffset(this string? offsetStr, out TimeSpan offset) {
            offset = default; 
            if (!IsValidTimeZone(offsetStr)) { return false; }
            var m = regTimeZone.Match(offsetStr!);
            if (m.Success) {
                var minutes = (m.Groups[1].Value == "-" ? -1 : 1)
                    * (m.Groups[2].Value.GetInt() * 60 + m.Groups[offsetStr!].Value.GetInt());
                offset = TimeSpan.FromMinutes(minutes);
                return true;
            }
            m = regTimeZone2.Match(offsetStr!);
            offset = TimeSpan.FromMinutes((m.Groups[1].Value == "-" ? 1 : -1)
                    * m.Groups[2].Value.GetInt());
            return true;
        }
        /// <summary>
        /// 从<paramref name="offsetStr"/>获取时区偏移量（分钟）
        /// </summary>
        /// <param name="offsetStr"></param>
        /// <returns></returns>
        public static string FixTimeZone(string offsetStr) {
            if (regTimeZone.IsMatch(offsetStr)) {
                return offsetStr;
            }
            var m = regTimeZone2.Match(offsetStr);
            var min = (m.Groups[1].Value == "-" ? 1 : -1) * m.Groups[2].Value.GetInt();
            var absMin = Math.Abs(min);
            return $"{(min < 1 ? "-" : "+")}{absMin / 60:00}:{absMin % 60:00}";
        }
        #endregion
        #region 时间戳
        /// <summary>
        /// 获取UNIX时间戳，精确到秒
        /// </summary>
        /// <returns></returns>
        public static long GetUnixTimestampSeconds() {
            return DateTime.UtcNow.ToUnixStampDateTime();
        }

        /// <summary>
        /// 获取UNIX时间戳，精确到毫秒
        /// </summary>
        /// <returns></returns>
        public static long GetUnixTimestampMilliseconds() {
            return DateTime.UtcNow.ToUnixTimestampMilliseconds();
        }

        /// <summary>
        /// 将DateTime转换为UNIX时间戳，精确到秒
        /// </summary>
        /// <param name="dateTime">时间</param>
        /// <returns>UNIX时间戳，精确到秒</returns>
        public static long ToUnixStampDateTime(this DateTime dateTime) {
            return (long)(dateTime.ToUniversalTime() - UNIX_EPOCH).TotalSeconds;
        }
        /// <summary>
        /// 将DateTime转换为UNIX时间戳，精确到毫秒
        /// </summary>
        /// <param name="dateTime">时间</param>
        /// <returns>UNIX时间戳，精确到秒</returns>
        public static long ToUnixTimestampMilliseconds(this DateTime dateTime) {
            return (long)(dateTime.ToUniversalTime() - UNIX_EPOCH).TotalMilliseconds;
        }

        /// <summary>
        /// 将UNIX时间戳（精确到秒）转换为DateTime
        /// (ToDateTimeFromUnixStamp)
        /// </summary>
        /// <param name="timestamp">UNIX时间戳，精确到秒</param>
        /// <returns>时间</returns>
        public static DateTime FromUnixStamp(this long? timestamp) {
            return timestamp.HasValue ? UNIX_EPOCH.Add(TimeSpan.FromSeconds(timestamp.Value)) : default;
        }
        /// <summary>
        /// 将UNIX时间戳（精确到秒）转换为DateTime
        /// </summary>
        /// <param name="timestamp">UNIX时间戳，精确到秒</param>
        /// <returns></returns>
        public static DateTime FromUnixStamp(this long timestamp) => UNIX_EPOCH.Add(TimeSpan.FromSeconds(timestamp));
        #endregion
    }
}
